// ntpc
package ntpclient

import (
	"bytes"
	"encoding/binary"

	// "encoding/hex"
	"fmt"
	"log"
	"net"

	// "net/url"
	"strings"
	"time"
)

//Unix 时间是一个开始于 1970 年的纪元（或者说从 1970 年开始的秒数）。然而 NTP 使用的是另外一个纪元，从 1900 年开始的秒数。
//两者相差70年，其秒数差值
const UNIX_STA_TIMESTAMP int64 = 2208988800
const (
	NTP_SERVER_IP = "0.cn.pool.ntp.org" /*NTP IP*/
	NTP_PORT_STR  = "123"               /*NTP专用端口号字 符串*/
	NTP_PCK_LEN   = 48
	LI            = 0
	VN            = 3
	MODE          = 3
	STRATUM       = 0
	POLL          = 4
	PREC          = -6
)

//NTP协议是基于UDP进行传输的，使用端口号为123。

// NTP时间戳是一个64位无符号浮点数，整数部分为高32位，小数部分为低32位；单位为秒。
// 时间起点为1900年1月1日零点。
// 在1968年NTP时间戳的最高位已被设置为1，在2036年64位字段将会溢出，所有位将会被置为零，此时的时间戳将会被视为无效。
// 为了解决这一问题，一种可能的办法为：如果最高位设置为1，UTC时间范围为1968-2036之间，时间计算起点从1900年1月0点0分0秒开始计算；如果最高位设置为0，UTC时间范围为2036-2104之间，时间计算起点从2036年2月7日6点28分16秒开始计算；

// 时钟同步报文
type Ntp struct {
	//1:32bits

	//B0.[7-6]一个2bit的代码，表示在NTP时间标尺中将要插入的下一跳情况。值为“11”时表示告警状态，时钟不能被同步。
	Li uint8

	//B0.[5-3]3 bits NTP的版本号。
	Vn uint8

	//B0.[2-0]3 bits NTP的工作模式
	//1：symmetric active，主动对等体模式。2：symmetric passive，被动对等体模式。
	//3：client，客户模式。4：server，服务器模式。5：broadcast，广播模式。
	//6：reserved for NTP control messages，NTP控制报文。
	Mode uint8

	//B1 8 bit 时钟的层数，定义了时钟的准确度。层数为1的时钟准确度最高，从1到15依次递减。
	Stratum uint8

	//B2 8比特	轮询时间，即发送报文的最小间隔时间。
	// 本地机和远程服务器多少时间进行一次同步(单位为秒). 在一开始运行NTP的时候这个poll值会比较小,那样和服务器同步的频率也就增加了,可以尽快调整到正确的时间范围.之后poll值会逐渐增大,同步的频率也就会相应减小
	Poll uint8

	//B3 8比特	时钟的精度。
	Precision uint8

	//B[4-7]32比特	到主参考时钟的总往返延迟时间。
	RootDelay int32

	//B[8-11] 32比特	本地时钟相对于主参考时钟的最大误差。
	RootDispersion int32

	//B[12-15] 32比特	 参考时钟源 的标识
	ReferenceIdentifier int32

	//B[16-23] 64位时间戳
	// 64比特	本地时钟最后一次被设定或更新的时间。如果值为0表示本地时钟从未被同步过。
	ReferenceTimestamp uint64
	//B[24-31] 64比特	 NTP请求报文离开发送端时发送端的本地时间。
	OriginateTimestamp uint64
	//B[32-39] 64比特	 NTP请求报文到达接收端时接收端的本地时间。
	ReceiveTimestamp uint64
	//B[40-47] 64比特 应答报文离开应答者时应答者的本地时间。
	TransmitTimestamp uint64 //指示服务器向客户发时间戳的时间
	// 96比特	（可选）验证信息。
	Authenticator []byte
}

func (this *Ntp) GetModeStr() string {
	switch this.Mode {
	case 3:
		return "client"
	case 4:
		return "server"
	default:
		return fmt.Sprintf("%d", this.Mode)
	}
}

func (this *Ntp) GetRefIdStr() string {
	return fmt.Sprintf("%d", this.ReferenceIdentifier)
}
func (this *Ntp) GetRefTime() string {
	return fmt.Sprintf("%d/%s", this.ReferenceTimestamp, NtpTimestamp2Time(this.ReferenceTimestamp).Format(time.RFC3339Nano))
}
func (this *Ntp) GetOriTime() string {
	return fmt.Sprintf("%d/%s", this.OriginateTimestamp, NtpTimestamp2Time(this.OriginateTimestamp).Format(time.RFC3339Nano))
}
func (this *Ntp) GetRecvTime() string {
	return fmt.Sprintf("%d/%s", this.ReceiveTimestamp, NtpTimestamp2Time(this.ReceiveTimestamp).Format(time.RFC3339Nano))
}
func (this *Ntp) GetTranTime() string {
	return fmt.Sprintf("%d/%s", this.TransmitTimestamp, NtpTimestamp2Time(this.TransmitTimestamp).Format(time.RFC3339Nano))
}
func (this *Ntp) String() string {
	ss := []string{fmt.Sprintf("模式:%s,版本:%d,层数:%d,间隔:%d,精度:%d", this.GetModeStr(), this.Vn, this.Stratum, this.Poll, this.Precision)}
	ss = append(ss, fmt.Sprintf("往返耗时:%d, 最大误差:%d", this.RootDelay, this.RootDispersion))
	ss = append(ss, fmt.Sprintf("参考时钟ID:%s, 上次同步:%s", this.GetRefIdStr(), this.GetRefTime()))
	ss = append(ss, fmt.Sprintf("请求客户时戳:%s, 服务器接收时戳:%s, %s", this.GetOriTime(), this.GetRecvTime(), this.GetTranTime()))

	// ss = append(ss, fmt.Sprintf("参考时钟:%d, 上次同步:%d", this.ReferenceIdentifier, this.ReferenceTimestamp))
	// ss = append(ss, fmt.Sprintf("参考时钟:%d, 上次同步:%d", this.ReferenceIdentifier, this.ReferenceTimestamp))
	// ss = append(ss, fmt.Sprintf("参考时钟:%d, 上次同步:%d", this.ReferenceIdentifier, this.ReferenceTimestamp))

	return strings.Join(ss, "\n")
}

func NewNtpV3Client() (p *Ntp) {
	//其他参数通常都是服务器返回的
	p = &Ntp{Li: 0, Vn: 3, Mode: 3, Stratum: 0}
	p.OriginateTimestamp = Time2NtpTimestamp(time.Now())
	return p
}
func NewNtp() (p *Ntp) {
	//其他参数通常都是服务器返回的
	p = &Ntp{Li: 0, Vn: 3, Mode: 3, Stratum: 0}
	return p
}

/**
构建NTP协议信息,大端字节排序
*/
func (this *Ntp) Encode() []byte {
	buf := &bytes.Buffer{}
	head := (this.Li << 6) | (this.Vn << 3) | ((this.Mode << 5) >> 5)
	binary.Write(buf, binary.BigEndian, uint8(head))
	binary.Write(buf, binary.BigEndian, this.Stratum)
	binary.Write(buf, binary.BigEndian, this.Poll)
	binary.Write(buf, binary.BigEndian, this.Precision)
	//写入其他字节数据
	binary.Write(buf, binary.BigEndian, this.RootDelay)
	binary.Write(buf, binary.BigEndian, this.RootDispersion)
	binary.Write(buf, binary.BigEndian, this.ReferenceIdentifier)
	binary.Write(buf, binary.BigEndian, this.ReferenceTimestamp)
	binary.Write(buf, binary.BigEndian, this.OriginateTimestamp)
	binary.Write(buf, binary.BigEndian, this.ReceiveTimestamp)
	binary.Write(buf, binary.BigEndian, this.TransmitTimestamp)
	//[27 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
	return buf.Bytes()
}

func (this *Ntp) Decode(bf []byte) {
	var (
		bit8  uint8
		bit32 int32
		bit64 uint64
		rb    *bytes.Reader
	)
	//貌似这binary.Read只能顺序读，不能跳着读，想要跳着读只能使用切片bf
	rb = bytes.NewReader(bf)
	binary.Read(rb, binary.BigEndian, &bit8)
	//向右偏移6位得到前两位LI即可
	this.Li = bit8 >> 6
	//向右偏移2位,向右偏移5位,得到前中间3位
	this.Vn = (bit8 << 2) >> 5
	//向左偏移5位，然后右偏移5位得到最后3位
	this.Mode = (bit8 << 5) >> 5
	binary.Read(rb, binary.BigEndian, &bit8)
	this.Stratum = bit8
	binary.Read(rb, binary.BigEndian, &bit8)
	this.Poll = bit8
	binary.Read(rb, binary.BigEndian, &bit8)
	this.Precision = bit8

	//32bits
	binary.Read(rb, binary.BigEndian, &bit32)
	this.RootDelay = bit32
	binary.Read(rb, binary.BigEndian, &bit32)
	this.RootDispersion = bit32
	binary.Read(rb, binary.BigEndian, &bit32)
	this.ReferenceIdentifier = bit32

	//以下几个字段都是64位时间戳(NTP都是64位的时间戳)
	binary.Read(rb, binary.BigEndian, &bit64)
	this.ReferenceTimestamp = bit64
	binary.Read(rb, binary.BigEndian, &bit64)
	this.OriginateTimestamp = bit64
	binary.Read(rb, binary.BigEndian, &bit64)
	this.ReceiveTimestamp = bit64
	binary.Read(rb, binary.BigEndian, &bit64)
	this.TransmitTimestamp = bit64
	//转换为unix时间戳,先左偏移32位拿到64位时间戳的整数部分，然后ntp的起始时间戳 1900年1月1日 0时0分0秒 2208988800
	// if useUnixSec {
	// 	this.ReferenceTimestamp = (this.ReceiveTimestamp >> 32) - UNIX_STA_TIMESTAMP
	// 	if this.OriginateTimestamp > 0 {
	// 		this.OriginateTimestamp = (this.OriginateTimestamp >> 32) - UNIX_STA_TIMESTAMP
	// 	}
	// 	this.ReceiveTimestamp = (this.ReceiveTimestamp >> 32) - UNIX_STA_TIMESTAMP
	// 	this.TransmitTimestamp = (this.TransmitTimestamp >> 32) - UNIX_STA_TIMESTAMP
	// }
}

// func main() {
// 	//链接阿里云NTP服务器,NTP有很多免费服务器可以使用time.windows.com
// 	// /*go tryNtp("time.windows.com")
// 	// try*/Ntp("ntp1.aliyun.com")

// 	for _, host := range strings.Split("ntp1.aliyun.com,time.windows.com,1.cn.pool.ntp.org,cn.pool.ntp.org,tw.pool.ntp.org", ",") {
// 		if t, s, e := SNTP_SyncTimeFrom(host); e == nil {
// 			fmt.Println(host, s, t.Format(time.RFC3339Nano))
// 		} else {
// 			fmt.Println(host, "Err:", e)
// 		}
// 	}
// }

func NtpTimestamp2Time(ntp uint64) time.Time {
	s := ntp>>32 - uint64(UNIX_STA_TIMESTAMP)
	n := (ntp << 32) >> 32
	return time.Unix(int64(s), int64(n))
}
func Time2NtpTimestamp(t time.Time) uint64 {
	u := uint64(t.Unix()) + uint64(UNIX_STA_TIMESTAMP)
	u = (u << 32) + uint64(t.Nanosecond())
	return u
}

func tryNtp(host string) {
	var (
		ntp    *Ntp
		buffer []byte
		err    error
		ret    int
	)
	conn, err := net.Dial("udp", host+":123")
	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
		conn.Close()
	}()

	for i := 0; i < 5; i++ {
		ntp = NewNtpV3Client()

		req := ntp.Encode()
		fmt.Println(len(req), host, ntp.GetOriTime())
		conn.Write(req)
		buffer = make([]byte, 2048)
		ret, err = conn.Read(buffer)
		if err == nil {
			if ret > 0 {
				ntp.Decode(buffer)
				log.Println(ret, host, ntp.String())
			}
		}
		time.Sleep(time.Minute)
	}
}
func SNTP_Req() []byte {
	req := make([]byte, 48, 48)
	Li, Vn, Mode := 0, 3, 3
	head := (Li << 6) | (Vn << 3) | ((Mode << 5) >> 5)
	req[0] = byte(head)
	return req
}
func SNTP_ParseTransmitTimestamp(ntp_res []byte) time.Time {
	if len(ntp_res) >= 48 {
		var transmitTimestamp uint64
		rd := bytes.NewReader(ntp_res[40:])
		binary.Read(rd, binary.BigEndian, &transmitTimestamp)
		return NtpTimestamp2Time(transmitTimestamp)
	}
	return time.Time{}
}
func SNTP_SyncTimeFrom(ntp_host string) (time.Time, int, error) {
	// if uri, err := url.Parse(ntp_host); err != nil {
	// 	return time.Time{}, fmt.Errorf("InvalidNtpHost:[%s]:%s", ntp_host, err.Error())
	// } else {
	// 	if p := uri.Port(); p == "" {
	// 		ntp_host = uri.Host + ":123"
	// 	} else {
	// 		ntp_host = uri.Host
	// 	}
	// }
	if strings.Index(ntp_host, ":") < 0 {
		ntp_host += ":123"
	}
	conn, err := net.Dial("udp", ntp_host)
	if err != nil {
		return time.Time{}, 0, err
	}
	defer conn.Close()

	req := SNTP_Req()
	conn.Write(req)
	// fmt.Println("send", hex.EncodeToString(req))
	// fmt.Println("send", hex.EncodeToString(NewNtpV3Client().Encode()))
	buffer := make([]byte, 64)
	// log.Println("wait...")
	ret, err := conn.Read(buffer)
	// log.Println("recv...", ret, err)
	if err != nil {
		return time.Time{}, 0, err
	}
	buffer = buffer[:ret]
	// fmt.Println("recv", ret, hex.EncodeToString(buffer))

	return SNTP_ParseTransmitTimestamp(buffer), int(buffer[1]), nil
}
